<?php
#
# UNLVSpatial: a dmBridge module for spatial content
#
# Copyright © 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * These template helpers use version 3 of the Google Maps API.
 *
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 * @link http://code.google.com/apis/maps/documentation/javascript/
 */
class UNLVSpatialTemplateHelper extends DMCustomTemplateHelper
implements DMTemplateHelper {

	/**
	 * Delegate method
	 * 
	 * @return void
	 */
	public function helperWillRender() {
		$this->addBodyScriptTag("http://maps.google.com/maps/api/js?sensor=false");
		$this->addBodyScriptTag(
				dirname($_SERVER['PHP_SELF']) . "/includes/js/jquery-1.6.min.js");
		$this->addBodyScriptTag(
				dirname($_SERVER['PHP_SELF']) . "/includes/js/jquery.scrollTo-1.4.2-min.js");
	}

	/**
	 * Renders a Google Map using either pinpoint (for UNLVSpatialPointObjects)
	 * or a rectangle (for UNLVSpatialRectObjects) to represent an object's
	 * location.
	 *
	 * @param string map_type One of ROADMAP, HYBRID, SATELLITE, or TERRAIN
	 * @param int point_zoom_level The default zoom level. Only affects points;
	 * auto-calculated for rects.
	 * @param string rect_stroke_color Valid CSS3 color
	 * @param int rect_stroke_weight
	 * @param string rect_fill_color Valid CSS3 color
	 * @param float rect_fill_opacity
	 * @return string HTML tags
	 */
	public function getHtmlGoogleMapsObject($map_type = "TERRAIN",
			$point_zoom_level = 11,
			$rect_stroke_color = "#0000ff",
			$rect_stroke_weight = 1,
			$rect_fill_color = "#0000ff",
			$rect_fill_opacity = 0.3) {
		if ($this->getSpatialObject()->getSpatialModelClassName()
				== "UNLVSpatialRectObject") {
			return $this->getHtmlGoogleMapsObjectRect($map_type,
				$rect_stroke_color, $rect_stroke_weight,
				$rect_fill_color, $rect_fill_opacity);
		} else {
			return $this->getHtmlGoogleMapsObjectPinpoint($map_type,
				$point_zoom_level);
		}
	}

	private function getHtmlGoogleMapsObjectPinpoint($map_type = "TERRAIN",
			$point_zoom_level = 11) {
		$obj = $this->getSpatialObject();
		try {
			$title = $obj->getMetadata("title");
			$title = str_replace('"', '\"', $title);

			return '<script type="text/javascript">
				function initialize() {
					var latLong = new google.maps.LatLng('
						. $obj->getSpatialCenterLat() . ',' . $obj->getSpatialCenterLong() . ');
					var mapOptions = {
						scrollwheel: false,
						zoom: ' . $point_zoom_level . ',
						center: latLong,
						mapTypeId: google.maps.MapTypeId.' . $map_type . '
					}
					var map = new google.maps.Map(
						document.getElementById(
							"UNLVSpatialMapCanvasObjectPinpoint"),
						mapOptions);
					var marker = new google.maps.Marker({
						position:latLong,
						map:map,
						title:"'. $title . '"
					});
				}
				window.onload = function() {
					initialize();
				}
				</script>
				<div id="UNLVSpatialMapCanvasObjectPinpoint"
					class="UNLVSpatialMapCanvas"></div>';
		} catch (DMNoSuchMethodException $e) {
			return "<!-- Map not inserted, as the current object does not have
				spatial information associated with it. -->";
		}
	}

	private function getHtmlGoogleMapsObjectRect($map_type = "TERRAIN",
			$stroke_color = "#0000ff", $stroke_weight = 1,
			$fill_color = "#0000ff", $fill_opacity = 0.3) {
		$obj = $this->getSpatialObject();
		try {
			return '<script type="text/javascript">
				function initialize() {
					var latLongNE = new google.maps.LatLng('
						. $obj->getSpatialNorthLat() . ',' . $obj->getSpatialEastLong() . ');
					var latLongSW = new google.maps.LatLng('
						. $obj->getSpatialSouthLat() . ',' . $obj->getSpatialWestLong() . ');
					var latLongBounds = new google.maps.LatLngBounds(
						latLongSW, latLongNE);
					var mapOptions = {
						scrollwheel: false,
						zoom:0,
						center: new google.maps.LatLng('
							. $obj->getSpatialCenterLat() . ',' . $obj->getSpatialCenterLong() . '),
						mapTypeId: google.maps.MapTypeId.' . $map_type . '
					}
					var map = new google.maps.Map(
						document.getElementById(
							"UNLVSpatialMapCanvasObjectPinpoint"),
						mapOptions);
					var rect = new google.maps.Rectangle({
						bounds:latLongBounds,
						map:map,
						fillColor:"' . $fill_color . '",
						fillOpacity:' . $fill_opacity . ',
						strokeColor:"' . $stroke_color . '",
						strokeWeight:' . $stroke_weight . '
					});
					map.fitBounds(latLongBounds);
				}
				window.onload = function() {
					initialize();
				}
				</script>
				<div id="UNLVSpatialMapCanvasObjectPinpoint"
					class="UNLVSpatialMapCanvas"></div>';
		} catch (DMNoSuchMethodException $e) {
			return "<!-- Map not inserted, as the current object does not have
				spatial information associated with it. -->";
		}
	}

	/**
	 * Renders a Google Map using either pinpoints (for
	 * UNLVSpatialPointObjects) or rectangles (for UNLVSpatialRectObjects) to
	 * represent the locations of multiple objects in a result set.
	 *
	 * @param string map_type One of ROADMAP, HYBRID, SATELLITE, or TERRAIN
	 * @param boolean display_rects_as_points
	 * @param string rect_stroke_color Valid CSS3 color
	 * @param int rect_stroke_weight
	 * @param string rect_fill_color Valid CSS3 color
	 * @param float rect_fill_opacity Float from 0 to 1; note that overlapping
	 * rects are additive.
	 * @return string HTML tags
	 */
	public function getHtmlGoogleMapsObjectResults($map_type = "TERRAIN",
			$display_rects_as_points = true,
			$rect_stroke_color = "#0000ff",
			$rect_stroke_weight = 1,
			$rect_fill_color = "#0000ff",
			$rect_fill_opacity = 0.05) {
		$objects = $this->getSpatialObjects();
		$obj_count = count($objects);
		if (!$obj_count) {
			return "<!-- Map not inserted, as no current objects have
				spatial information associated with them. -->";
		}

		$rects_and_markers = "";
		$lats = $longs = array();
		$nlats = $slats = $wlongs = $elongs = array();
		for ($i = 0; $i < $obj_count; $i++) {
			if ($objects[$i]->getSpatialModelClassName() == "UNLVSpatialRectObject"
					&& !$display_rects_as_points) { // append a rect

				$nlats[$i] = $objects[$i]->getSpatialNorthLat();
				$elongs[$i] = $objects[$i]->getSpatialEastLong();
				$slats[$i] = $objects[$i]->getSpatialSouthLat();
				$wlongs[$i] = $objects[$i]->getSpatialWestLong();
				$rects_and_markers .= 'var latLongNE = new google.maps.LatLng('
							. $nlats[$i] . ',' . $elongs[$i] . ');
						var latLongSW = new google.maps.LatLng('
							. $slats[$i] . ',' . $wlongs[$i] . ');
						var latLongBounds = new google.maps.LatLngBounds(
							latLongSW, latLongNE);
						var rect = new google.maps.Rectangle({
						bounds:latLongBounds,
						map:map,
						fillColor:"' . $rect_fill_color . '",
						fillOpacity:' . $rect_fill_opacity . ',
						strokeColor:"' . $rect_stroke_color . '",
						strokeWeight:' . $rect_stroke_weight . '
					});
					map.fitBounds(latLongBounds);
					google.maps.event.addListener(rect, "click", function() {
						var selector = "table.dmResults tr:eq(' . $i . ')";
						$.scrollTo(selector, 500, "swing");
					});';
			} else { // append a marker
				$lats[$i] = $objects[$i]->getSpatialCenterLat();
				$longs[$i] = $objects[$i]->getSpatialCenterLong();

				$title = str_replace('"', '\"', $objects[$i]->getMetadata("title"));
				$rects_and_markers .= 'var markerLL = new google.maps.LatLng('
							. $lats[$i] . ',' . $longs[$i] . ');
						var marker = new google.maps.Marker({
						position:markerLL,
						map:map,
						title:"' . $title . '"
					});
					google.maps.event.addListener(marker, "click", function() {
						var selector = "table.dmResults tr:eq(' . $i . ')";
						$.scrollTo(selector, 500, "swing");
					});
					bounds.extend(markerLL);';
			}
		}

		// calculate map center (average of highest & lowest lat/long)
		$nlats = array_merge($nlats, $lats);
		$slats = array_merge($slats, $lats);
		$wlongs = array_merge($wlongs, $longs);
		$elongs = array_merge($elongs, $longs);
		$map_center_lat = (max($nlats) + min($slats)) / 2;
		$map_center_long = ((max($elongs) + min($wlongs)) / 2);

		return '<script type="text/javascript">
			function initialize() {
				var center = new google.maps.LatLng('
					. $map_center_lat . ',' . $map_center_long . ');
				var bounds = new google.maps.LatLngBounds();
				var mapOptions = {
					scrollwheel: false,
					zoom: 8,
					center: center,
					mapTypeId: google.maps.MapTypeId.' . $map_type . '
				}
				var map = new google.maps.Map(
					document.getElementById(
						"UNLVSpatialMapCanvasObjectResultPinpoints"),
					mapOptions);' . $rects_and_markers . '
				map.fitBounds(bounds);
			}
			window.onload = function() {
				initialize();
			}
			</script>
			<div id="UNLVSpatialMapCanvasObjectResultPinpoints"
				class="UNLVSpatialMapCanvas"></div>';
	}

	/**
	 * @param float center_lat
	 * @param float center_long
	 * @param string map_type One of ROADMAP, HYBRID, SATELLITE, or TERRAIN
	 * @param int zoom_level
	 * @return string HTML tags
	 */
	public function getHtmlGoogleMapsSpatialSearch($center_lat, $center_long,
			$map_type = "TERRAIN", $zoom_level = 8) {
		$form_action = DMInternalURI::getURIWithParams("objects/spatial_results");
		$tmp = explode("/", DMHTTPRequest::getCurrent()->getURI()->getParams());
		if (count($tmp) > 1 && $tmp[0] == "objects") {
			try {
				$col = DMCollectionFactory::getCollection("/" . trim($tmp[1], "/"));
				$form_action = DMInternalURI::getURIWithParams(
						"objects" . $col->getAlias() . "/spatial_results");
			} catch (DMUnavailableModelException $e) {
				// invalid collection, skip it
			}
		}
		
		return '<script type="text/javascript">
			function initialize() {
				var center = new google.maps.LatLng('
					. $center_lat . ',' . $center_long . ');
				var mapOptions = {
					scrollwheel: false,
					zoom: ' . $zoom_level . ',
					center: center,
					mapTypeId: google.maps.MapTypeId.' . $map_type . '
				}
				var map = new google.maps.Map(
					document.getElementById(
						"UNLVSpatialMapCanvasSpatialSearch"),
					mapOptions);
				document.getElementById("UNLVSpatialSubmit").onclick = function() {
					ne = map.getBounds().getNorthEast();
					sw = map.getBounds().getSouthWest();
					document.getElementById("lat_n").value = ne.lat();
					document.getElementById("lat_s").value = sw.lat();
					document.getElementById("long_w").value = sw.lng();
					document.getElementById("long_e").value = ne.lng();
				}
			}
			window.onload = function() {
				initialize();
			}
			</script>
			<form name="UNLVSpatialSearch"
					action="' . $form_action . '">
				<div id="UNLVSpatialMapCanvasSpatialSearch"
					class="UNLVSpatialMapCanvas"></div>
				<fieldset id="UNLVSpatialSearch">
					<h5>Search for objects:</h5>
					<ul id="UNLVSpatialSearchType">
						<li><label><input type="radio" name="mode" value="within"
							checked="checked">Entirely within viewport</label></li>
						<li><label><input type="radio" name="mode" value="intersect"
							>At least partially within viewport</label></li>
						<li><label><input type="radio" name="mode" value="outside"
							>Outside viewport</label></li>
					</ul>
					<h5>In collections:</h5>
					<div class="dmSearchCollectionList">
						<ul>' . $this->getCollectionListElements() . '</ul>
						<p>
							<input id="lat_n" type="hidden" name="lat_n">
							<input id="lat_s" type="hidden" name="lat_s">
							<input id="long_w" type="hidden" name="long_w">
							<input id="long_e" type="hidden" name="long_e">
							<div class="dmCheckAll">
								' . $this->getCheckAllLinks("UNLVSpatialSearch") . '
							</div>
						</p>
					</div>
					<input id="UNLVSpatialSubmit" class="dmSubmit" type="submit" value="Search">
				</fieldset>
			</form>';
	}

	/**
	 * Summarizes UNLVSpatialIndexer indexer results in HTML tables in the
	 * Control Panel. Not for public use.
	 * 
	 * @return string HTML div element
	 */
	public function getHtmlIndexerSummary() {
		$indexer = $this->getView()->getVar("indexer");
		if (!$indexer) {
			return "";
		}
		$dxml = new DMDOMDocument("1.0", "utf-8");
		$dxml->loadXML("<div/>");

		// indexed objects
		$fieldset = $dxml->createElement("fieldset");
		$legend = $dxml->createElement("legend", "Indexed objects");
		$fieldset->appendChild($legend);
		$dxml->documentElement->appendChild($fieldset);
		$table = $dxml->createElement("table");
		$fieldset->appendChild($table);
		$table->setAttribute("cellpadding", 0);
		$table->setAttribute("cellspacing", 2);
		$table->setAttribute("border", 0);
		$table->setAttribute("class", "columnar");
		$tr = $dxml->createElement("tr");
		$table->appendChild($tr);
		$th = $dxml->createElement("th", "Pointer");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "Title");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "N Lat");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "S Lat");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "W Long");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "E Long");
		$tr->appendChild($th);

		$i = 0;
		foreach ($indexer->getIndexedObjects() as $obj) {
			$tr = $dxml->createElement("tr");
			if ($i & 1) {
				$tr->setAttribute("class", "odd");
			}
			$table->appendChild($tr);
			$td = $dxml->createElement("td", $obj->getPtr());
			$tr->appendChild($td);

			$a = $dxml->createElement("a",
					DMString::xmlentities($obj->getMetadata("title")));
			$a->setAttribute("href", $obj->getURI());
			$td = $dxml->createElement("td");
			$td->appendChild($a);
			$tr->appendChild($td);

			$td = $dxml->createElement("td", $obj->getSpatialNorthLat());
			$tr->appendChild($td);
			$td = $dxml->createElement("td", $obj->getSpatialSouthLat());
			$tr->appendChild($td);
			$td = $dxml->createElement("td", $obj->getSpatialWestLong());
			$tr->appendChild($td);
			$td = $dxml->createElement("td", $obj->getSpatialEastLong());
			$tr->appendChild($td);
			$i++;
		}

		// skipped objects
		$fieldset = $dxml->createElement("fieldset");
		$legend = $dxml->createElement("legend", "Skipped objects");
		$fieldset->appendChild($legend);
		$dxml->documentElement->appendChild($fieldset);
		$table = $dxml->createElement("table");
		$fieldset->appendChild($table);
		$table->setAttribute("cellpadding", 0);
		$table->setAttribute("cellspacing", 2);
		$table->setAttribute("border", 0);
		$table->setAttribute("class", "columnar");
		$tr = $dxml->createElement("tr");
		$table->appendChild($tr);
		$th = $dxml->createElement("th", "Pointer");
		$tr->appendChild($th);
		$th = $dxml->createElement("th", "Title");
		$tr->appendChild($th);

		$i = 0;
		foreach ($indexer->getSkippedObjects() as $obj) {
			$tr = $dxml->createElement("tr");
			if ($i & 1) {
				$tr->setAttribute("class", "odd");
			}
			$table->appendChild($tr);
			$td = $dxml->createElement("td", $obj->getPtr());
			$tr->appendChild($td);
			$a = $dxml->createElement("a",
					DMString::xmlentities($obj->getMetadata("title")));
			$a->setAttribute("href", $obj->getURI());
			$td = $dxml->createElement("td");
			$td->appendChild($a);
			$tr->appendChild($td);
			$i++;
		}

		return $dxml->saveHTML($dxml->documentElement);
	}

	private function getCheckAllLinks($form_name) {
		return '<div class="dmCheckAll">
				<a onclick="checkAll(' . $form_name . ')">Check All</a> |
				<a onclick="uncheckAll(' . $form_name . ')">Uncheck All</a>
			</div>
			<script type="text/javascript">
			function checkAll(formName) {
				for (i = 0; i < formName.length; i++) {
					if (formName[i].type == "checkbox") {
						formName[i].checked = true;
					}
				}
			}
			function uncheckAll(formName) {
				for (i = 0; i < formName.length; i++) {
					if (formName[i].type == "checkbox") {
						formName[i].checked = false;
					}
				}
			}
			</script>';
	}

	private function getCollectionListElements() {
		$tpl_set = $this->getView()->getTemplate()->getTemplateSet();
		if (count($tpl_set->getAuthorizedCollections()) == 1) {
			return null;
		}
		$collection_list = "";
		foreach (DMCollection::getAuthorized() as $c) {
			if (count($tpl_set->getAuthorizedCollections()) > 1
					&& !in_array($c, $tpl_set->getAuthorizedCollections())) {
				continue;
			}

			$input = DMHTTPRequest::getCurrent()->getSession()->getSearchInput();
			$checked = false;
			if ($input instanceof DMInput) {
				if (in_array($c->getAlias(), $input->getCollections())) {
					$checked = true;
				}
			} else {
				if ($this->getView()->getCollection()
						&& $this->getView()->getCollection()->getAlias()
						!= "/dmdefault") {
					$checked = ($c->equals($this->getView()->getCollection()));
				} else {
					$checked = true;
				}
			}
			$checked = $checked ? 'checked="checked"' : null;
			$collection_list .= sprintf(
				'<li>
					<label>
						<input type="checkbox" name="CISOROOT" value="%s" %s>
						%s
					</label>
				</li>',
				DMString::websafe($c->getAlias()),
				$checked,
				DMString::websafe($c->getName())
			);
			$checked = '';
		}
		return $collection_list;
	}

	/**
	 * @return DMObject The spatialized object associated with the current
	 * view, or null if it is not spatialized
	 */
	public function getSpatialObject() {
		$obj = $this->getView()->getObject();
		UNLVSpatialObjectFactory::spatializeObject($obj);
		try {
			$obj->getSpatialModelClassName();
			// that worked, so it must be a spatialized object
			return $obj;
		} catch (DMNoSuchMethodException $e) {
			// not spatialized, so skip it
		}
		return null;
	}

	/**
	 * @return array Array of spatialized objects associated with the current
	 * view
	 */
	public function getSpatialObjects() {
		$objects = $this->getView()->getQuery()->getSearchResults();
		$sp_objects = array();
		foreach ($objects as $obj) {
			UNLVSpatialObjectFactory::spatializeObject($obj);

			try {
				$obj->getSpatialModelClassName();
				// that worked, so it must be a spatialized object
				$sp_objects[] = $obj;
			} catch (DMNoSuchMethodException $e) {
				// not spatialized, so skip it
			}
		}
		return $sp_objects;
	}

}
